<?php

require_once 'PipesImpl/ModelList.php';
require_once 'PipesImpl/Model/Meta.php';
require_once 'PipesImpl/Visitable.php';
require_once 'Pipes/Exception.php';
require_once 'Pipes/Value/Callback.php';
require_once 'Pipes/Model/Mapper/Factory.php';
require_once 'PipesImpl/Config.php';

abstract class Pipes_ModelList implements PipesImpl_ModelList, PipesImpl_Visitable
{
    private $records = array();
    private $modelName = '';
    
    private $_cursor = 0;
    private $_cache  = array();
    private $_saveCallback = null;
    private $_removeCallback = null;

    private $_mapper = null;
    private $_config = null;
    private $_meta = null;
    
    public function __construct(PipesImpl_Config $config, PipesImpl_Model_Meta $meta, array $records)
    {
        $this->records = $records;
        reset($this->records);
        $this->modelName = substr(get_class($this), 0, -4);
        if (!class_exists($this->modelName)) {
            require_once str_replace('_', '/', $this->modelName) . '.php';
        }
        $this->_meta = $meta;

        $name = substr($this->modelName, strrpos($this->modelName, '_') + 1);
        $mapperFactory = new Pipes_Model_Mapper_Factory($config);
        $this->_mapper = $mapperFactory->factory($name);
        $this->_config = $config;
    }
    
    public function current() {
        if (!$this->valid()) {
            throw new Pipes_Exception('Past end of iterator');
        }
        if (!isset($this->_cache[$this->key()])) {
            $modelName = $this->modelName;
            $this->_cache[$this->key()] = new $modelName($this->_meta, $this->records[$this->key()]);
            $this->_cache[$this->key()]->attach(array($this->_mapper, 'save'));
        }
        
        return $this->_cache[$this->key()];
    }
    public function key() {
        return $this->_cursor; 
    }
    public function next()
    {
        $this->_cursor++; 
    }
    public function rewind() 
    {
        $this->_cursor = 0; 
    }
    public function valid() 
    {
        return ($this->_cursor < count($this->records));
    }
    
    
    public function item($key)
    {
        $this->_cursor = $key - 1;
        return $this->current();
    }
    
    public function find($id)
    {
        $record = current($this->records);
        reset ($record);
        return $this->_findByColumn(key($record), $id);
    }
    
    /**
     * @todo refactor into Locator 
     */
    protected function _findByColumn($column, $findValue) {
        foreach ($this->records as $record_cursor => $record) {
            foreach ($record as $key => $value) {
                if ($key == $column) {
                    if ($value == $findValue) {
                        $this->_cursor = $record_cursor;
                        return $this->current();
                    }
                }
            }
        }
        
    }
    
    public function __call($method, $arguments)
    {
        // todo: refactor into a Factory that returns a Locator and can provide
        // one-off factories to implement custom finding logic
        if (preg_match('/^findBy([A-Za-z0-9_]+)$/', $method, $matches)) {
            $column = $matches[1];
            return $this->_findByColumn(strtolower($column), array_shift($arguments));
        }
    }
    
    public function __get($key) {
        switch ($key) {
            case 'length' :
                return count($this->records);
                break;
            case 'meta' :
                return $this->_meta;
                break;
        }
    }

    public function attachSave($callback)
    {
        $this->_saveCallback = new Pipes_Value_Callback($callback);
    }

    public function attachRemove($callback) {
        $this->_removeCallback = new Pipes_Value_Callback($callback);
    }
    
    public function save()
    {
        if (is_null($this->_saveCallback)) {
            return;
        }
        
        foreach ($this->_cache as $model) {
            call_user_func($this->_saveCallback->value, $model);
        }
    }
    
    public function add(PipesImpl_Model $model = null)
    {
        $newKey = count($this->records);
        $this->records[] = !is_null($model) ? $model->toArray() : array();
        $this->_cursor = $newKey;
        return $this->current();
    }
    
    public function removeCurrent()
    {
        $model = $this->current();
        unset($this->_cache[$this->key()]);
        unset($this->_records[$this->key()]);

        if (!is_null($this->_removeCallback)) {
            call_user_func($this->_removeCallback->value, $model);
        }
    }

    public function accept(PipesImpl_Visitor $visitor)
    {
        $visitor->visit($this);
    }
    
    public function getName()
    {
        $exploded = explode('_', get_class($this));
        return substr(array_pop($exploded), 0, -4);
    }
}

